package cn.ibizlab.core.authentication.service.impl;

import cn.ibizlab.core.ad.domain.SysPerson;
import cn.ibizlab.core.authentication.domain.AuthProvider;
import cn.ibizlab.core.authentication.domain.LdapConfig;
import cn.ibizlab.core.authentication.mapper.LdapUserMapper;
import cn.ibizlab.core.authentication.provider.AuthenticationService;
import cn.ibizlab.util.enums.Entities;
import cn.ibizlab.util.errors.BadRequestAlertException;
import com.alibaba.fastjson.JSON;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.springframework.ldap.core.LdapTemplate;
import org.springframework.ldap.core.support.LdapContextSource;
import org.springframework.ldap.filter.AndFilter;
import org.springframework.ldap.filter.EqualsFilter;
import org.springframework.ldap.support.LdapNameBuilder;
import org.springframework.security.core.Authentication;
import org.springframework.util.DigestUtils;
import org.springframework.util.ObjectUtils;

import javax.naming.Name;
import javax.naming.directory.*;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import static cn.ibizlab.core.authentication.constants.AuthenticationConstant.*;

@Data
@Slf4j
public class LdapAuthenticationService implements AuthenticationService {

    String id;
    AuthProvider provider;
    LdapConfig ldapConfig;
    LdapTemplate ldapTemplate;

    /**
     * 根据provider构造LdapAuthenticationService
     * @param provider
     */
    public LdapAuthenticationService(AuthProvider provider){
        //ldap用户配置
        Map<String,String> userParamsMap = extractParams2Map(provider.getBaseConfig());
        //ldap默认配置
        Map<String,String> defaultLdapConfig = (Map<String, String>) provider.get("defaultConfig");

        LdapContextSource contextSource = new LdapContextSource();
        contextSource.setUrl(provider.getAuthenticationUri());
        contextSource.setBase(!ObjectUtils.isEmpty(userParamsMap.get(PROVIDER_LDAP_PARAM_BASE))? userParamsMap.get(PROVIDER_LDAP_PARAM_BASE): defaultLdapConfig.get(PROVIDER_LDAP_PARAM_BASE));
        contextSource.setUserDn(!ObjectUtils.isEmpty(userParamsMap.get(PROVIDER_LDAP_PARAM_USERNAME))? userParamsMap.get(PROVIDER_LDAP_PARAM_USERNAME): defaultLdapConfig.get(PROVIDER_LDAP_PARAM_USERNAME));
        contextSource.setPassword(!ObjectUtils.isEmpty(userParamsMap.get(PROVIDER_LDAP_PARAM_PASSWORD))? userParamsMap.get(PROVIDER_LDAP_PARAM_PASSWORD): defaultLdapConfig.get(PROVIDER_LDAP_PARAM_PASSWORD));
        contextSource.setPooled(false);
        contextSource.afterPropertiesSet();

        LdapTemplate ldapTemplate = new LdapTemplate(contextSource);
        //构造ldapConfig
        LdapConfig ldapConfig = new LdapConfig();
        ldapConfig.setCn(!ObjectUtils.isEmpty(userParamsMap.get(PROVIDER_LDAP_PARAM_UID))? userParamsMap.get(PROVIDER_LDAP_PARAM_UID): defaultLdapConfig.get(PROVIDER_LDAP_PARAM_UID));
        ldapConfig.setObjectClass(!ObjectUtils.isEmpty(userParamsMap.get(PROVIDER_LDAP_PARAM_OBJECT_CLASS))? userParamsMap.get(PROVIDER_LDAP_PARAM_OBJECT_CLASS): defaultLdapConfig.get(PROVIDER_LDAP_PARAM_OBJECT_CLASS));
        ldapConfig.setBaseOU(!ObjectUtils.isEmpty(userParamsMap.get(PROVIDER_LDAP_PARAM_OU_BASE))? userParamsMap.get(PROVIDER_LDAP_PARAM_OU_BASE): defaultLdapConfig.get(PROVIDER_LDAP_PARAM_OU_BASE));

        //构造lapTemplate
        this.ldapConfig = ldapConfig;
        this.ldapTemplate = ldapTemplate;
        this.provider = provider;
        this.id = DigestUtils.md5DigestAsHex(JSON.toJSONString(provider).getBytes());
    }

    /**
     * 身份认证
     * @param authentication 用户身份信息
     * @return
     */
    @Override
    public Authentication authenticate(Authentication authentication) {
        String loginName = !ObjectUtils.isEmpty(authentication.getPrincipal()) ? authentication.getPrincipal().toString() : null;
        String password = !ObjectUtils.isEmpty(authentication.getCredentials()) ? authentication.getCredentials().toString() : null;
        Boolean bAuthenticate;
        try {
            AndFilter filter = new AndFilter();
            if (!ObjectUtils.isEmpty(loginName)){
                filter.and(new EqualsFilter(ldapConfig.getCn(), loginName));
            }
            bAuthenticate = ldapTemplate.authenticate(ldapConfig.getBaseOU(), filter.encode(), password);
        } catch (RuntimeException e) {
            bAuthenticate = false;
            log.error(String.format("使用[%1$s]认证服务验证[%2$s]用户登录出现异常，异常信息：%3$s", provider.getName() , loginName, e.getMessage()));
        }
        log.debug(bAuthenticate? String.format("使用[%1$s]认证方式验证用户[%2$s]成功",provider.getName(),loginName) : String.format("使用[%1$s]认证方式验证用户[%2$s]失败",provider.getName(),loginName));

        return bAuthenticate? authentication : null;
    }

    /**
     * 创建用户
     * @param person
     */
    public void create(SysPerson person) {
        Name dn = buildDn(person.getUid());
        ldapTemplate.bind(dn, null, buildAttributes(person));
    }

    /**
     * 修改用户
     * @param person
     */
    public void update(SysPerson person ,String... fields) {
        if(ObjectUtils.isEmpty(fields))
            throw new BadRequestAlertException("未指定要修改的ldap属性",Entities.AUTH_USER.toString(),"updatePerson");
        Name dn = buildDn(person.getUid());
        ldapTemplate.modifyAttributes(dn, buildModifyAttributes(person,fields));
    }

    /**
     * 查询用户
     * @param username
     * @return
     */
    public SysPerson lookup(String username) {
        SysPerson user = null;
        try {
            Name dn = buildDn(username);
            user = ldapTemplate.lookup(dn , new LdapUserMapper());
        }catch (Exception e){
            log.error(String.format("使用[%1$s]认证服务验证[%2$s]用户登录出现异常，异常信息：%3$s", provider.getName() , username, e.getMessage()));
        }
        return user;
    }

    /**
     * 构建用户dn
     * @param username
     * @return
     */
    private Name buildDn(String username) {
        LdapNameBuilder builder = LdapNameBuilder.newInstance(ldapConfig.getBaseOU()).add(ldapConfig.getCn(), username);
        return builder.build();
    }

    /**
     * 用户属性映射
     * @param person
     * @return
     */
    private Attributes buildAttributes(SysPerson person) {

        //构建用户的属性
        BasicAttribute basicAttribute = new BasicAttribute("objectclass");
        basicAttribute.add(ldapConfig.getObjectClass());

        Attributes attributes = new BasicAttributes();
        attributes.put(basicAttribute);

        //填充属性值
        for(Map.Entry<String,String> entry : LdapUserMapper.ldapFieldMaps.entrySet()){
            String fieldName = entry.getKey();
            Object fieldValue = person.get(entry.getValue());
            if(!ObjectUtils.isEmpty(fieldValue))
                attributes.put(fieldName, fieldValue);
        }
        return attributes;
    }

    /**
     * 构造用户属性列表
     * @param person
     * @return
     */
    private ModificationItem[] buildModifyAttributes(SysPerson person, String [] fields) {
        List<ModificationItem> modificationItems = new ArrayList<>();
        for(String field : fields){
            String ldapFieldName = LdapUserMapper.ldapFieldMaps.get(field);
            if(!ObjectUtils.isEmpty(ldapFieldName)){
                Object fieldValue = person.get(field);
                ModificationItem item = new ModificationItem(DirContext.REPLACE_ATTRIBUTE, new BasicAttribute(ldapFieldName, fieldValue));
                modificationItems.add(item);
            }
        }
        return modificationItems.stream().toArray(ModificationItem[]::new);
    }



    /**
     * 参数转换：key = value 转 map
     * @param params
     * @return
     */
    protected Map<String, String> extractParams2Map(String params) {
        Map<String, String> paramMap = new HashMap<>();
        if(ObjectUtils.isEmpty(params))
            return paramMap;
        for(String param: params.split("(\n|;)")){
            if (param.contains("=")) {
                String[] pairs = param.split("=",2);
                if(pairs.length != 2 || ObjectUtils.isEmpty(pairs[0].trim()) || ObjectUtils.isEmpty(pairs[1].trim())){
                    throw new BadRequestAlertException(String.format("参数[%1$s]格式不正确",params), Entities.AUTH_LOGIN.toString(),"LdapLogin");
                }
                paramMap.put(pairs[0].trim(),pairs[1].trim());
            }
        }
        return paramMap;
    }

    /**
     * 将数据库用户存储到ldap中
     * @param person
     * @return
     */
    public boolean syncDatabaseUser2Ldap(SysPerson person){
        try{
            //查询ldap用户是否存在，不存在则添加用户
            SysPerson ldapUser = this.lookup(person.getUid());
            if(ldapUser == null){
                log.debug(String.format("正在尝试将[%1$s:%2$s]用户同步至[%3$s]",person.getUid(),person.getDisplayName(),provider.getName()));
                this.create(person);
                log.debug(String.format("成功将[%1$s:%2$s]用户同步至[%3$s]",person.getUid(),person.getDisplayName(),provider.getName()));
                return true;
            }
        }
        catch(Exception e){
            log.error(String.format("将[%1$s:%2$s]用户同步至[%3$s]出现异常，异常信息：%4$s",person.getUid(),person.getDisplayName(),provider.getName(),e.getMessage()));
        }
        return false;
    }

    /**
     * 获取服务类型
     * @return
     */
    public String getServiceType(){
        if(provider != null){
            return provider.getName();
        }
        return "ldap";
    }
}